#include <QtGui>
#include <QtNetwork>
#include "MainWindow.h"
#include "AboutWindow.h"
#include "../shared/Player.h"
#include "../shared/Bullet.h"
#include "../shared/Map.h"
#include "../shared/MovingObject.h"
//#include "../shared/PlayerStruct.h"
#include <iostream>


MainWindow::MainWindow(QWidget *parent) :
    QWidget(parent)
{
    setupUi(this);

    tcpServer = new QTcpServer(this);
    connect(tcpServer,         SIGNAL(newConnection()), this, SLOT(addClient()));
    connect(actionQuit,        SIGNAL(triggered()),     this, SLOT(close()));
    connect(actionAbout,       SIGNAL(triggered()),     this, SLOT(About()));
    connect(startServerButton, SIGNAL(clicked()),       this, SLOT(serverStart()));
    connect(stopServerButton,  SIGNAL(clicked()),       this, SLOT(serverStop()));
    //connect(kickButton,        SIGNAL(clicked()),       this, SLOT(kickPlayer()));
    connect(kickAllButton,     SIGNAL(clicked()),       this, SLOT(kickAllPlayers()));

    clientsList = new QList<QTcpSocket*>();
    playersList = new QList<Player*>();
    bulletsList = new QList<Bullet*>();
    blockSizeList = new QList<quint16>();

    stopServerButton->setEnabled(false);
    serverIPLabel->setEnabled(false);
    serverPortLabel->setEnabled(false);
    portLineEdit->setValidator(new QIntValidator(1, 65535, this));

    map = new Map(0, QPointF(0, 0));
    graphicsView->scene()->addItem(map);


    dT = 0.01;                           // interwal - 0.01 sekundy (10 milisekund)
    QTimer *timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, SLOT(GameTimer()));
    timer->start(1000*dT);

}


MainWindow::~MainWindow()
{
    delete clientsList;
    delete playersList;
    delete bulletsList;
    delete blockSizeList;
    delete tcpServer;
    delete map;
}

/*
void MainWindow::kickPlayer()
{
    int index = tableWidget->currentRow();

  //  std::cout << "CURE " << index << std::endl;
   // std::cout << "rozmiar " << clientsList->size() << std::endl;

   // graphicsView->scene()->removeItem(playersList->at(index));

    for (int i = 0; i < 3; i++)
        tableWidget->setItem(index, i, new QTableWidgetItem(""));
   // clientsList->removeAt(index);
    //playersList->removeAt(index);
    //
}
*/

void MainWindow::kickAllPlayers()
{

    qDeleteAll(clientsList->begin(), clientsList->end());
    clientsList->clear();
    qDeleteAll(playersList->begin(), playersList->end());
    playersList->clear();
    //qDeleteAll(blockSizeList->begin(), blockSizeList->end());
    //blockSizeList->clear();

}


void MainWindow::serverStart()
{
    if (!tcpServer->listen(QHostAddress::Any, portLineEdit->text().toInt()))
    {
        QMessageBox::critical(this, tr("Błąd!"), tr("Serwer nie może zostać poprawnie uruchomiony"));
        return;
    }
    else
    {
        QString ipAddress;
        QList<QHostAddress> ipAddressesList = QNetworkInterface::allAddresses();

        // szukaj swojego adresu (zeby nie byl typu localhost - 127.0.0.1)
        for (int i = 0; i < ipAddressesList.size(); ++i)
        {
            if (ipAddressesList.at(i) != QHostAddress::LocalHost && ipAddressesList.at(i).toIPv4Address())
            {
                ipAddress = ipAddressesList.at(i).toString();
                break;
            }
        }

        // gdy nie znajdziesz, uzyj localhost
        if (ipAddress.isEmpty())
        {
            ipAddress = QHostAddress(QHostAddress::LocalHost).toString();
        }

        serverIPLabel->setEnabled(true);
        serverIPLabel->setText(tr("Server IP : ") + ipAddress);
        serverPortLabel->setEnabled(true);
        serverPortLabel->setText(tr("Server port : %1").arg(tcpServer->serverPort()));
        startServerButton->setEnabled(false);
        stopServerButton->setEnabled(true);
    }
}


void MainWindow::serverStop()
{
    tcpServer->close();
    serverIPLabel->setText(tr("Serwer wyłączony"));
    serverPortLabel->setText(tr(""));
    startServerButton->setEnabled(true);
    stopServerButton->setEnabled(false);

    qDeleteAll(clientsList->begin(), clientsList->end());
    clientsList->clear();
    qDeleteAll(playersList->begin(), playersList->end());
    playersList->clear();
}


// gdy przychodzi nowe polaczenie, dodaj klienta do listy i skojarz z nim akcje
void MainWindow::addClient()
{

    //PlayerStruct *play = new PlayerStruct;
    // play->client = tcpServer->nextPendingConnection();
    // clientsList->push_back(play);

    QTcpSocket *client = tcpServer->nextPendingConnection();
    clientsList->push_back(client);

    Player *player = new Player(map, QPointF(120, 120), playersList->size()+1);
    playersList->push_back(player);
    //graphicsView->scene()->addItem(player);


    tableWidget->setItem(playersList->size()-1, 1, new QTableWidgetItem(QString::number(0)));
    tableWidget->setItem(playersList->size()-1, 2, new QTableWidgetItem(QString::number(0)));


    blockSizeList->push_back(0);

    connect(client, SIGNAL(disconnected()), this, SLOT(removeClient()));
    connect(client, SIGNAL(disconnected()), client, SLOT(deleteLater()));
    connect(client, SIGNAL(readyRead()), this, SLOT(readData()));

    QTimer *timer = new QTimer(this);
    timer->setInterval(65);
    timer->start();

    connect(timer, SIGNAL(timeout()), this, SLOT(sendData()));

}

// usuwa klienta, ktory wyslal sygnal
void MainWindow::removeClient()
{

    QTcpSocket *client = (QTcpSocket*) sender();

    int index = clientsList->indexOf(client);

    graphicsView->scene()->removeItem(playersList->at(index));

    // czysc tablice statystyk
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 5; j++)
        {
            tableWidget->setItem(j, i, new QTableWidgetItem(""));
        }

    clientsList->removeAt(index);
    playersList->removeAt(index);
    blockSizeList->removeAt(index);
}


void MainWindow::readData()
{

    QTcpSocket *client = (QTcpSocket*) sender();
    int index = clientsList->indexOf(client);

    QDataStream in(client);
    in.setVersion(QDataStream::Qt_4_0);

    if (blockSizeList->at(index) == 0)
    {
        if (client->bytesAvailable() < (int)sizeof(quint16))
        {
            return;
        }

        quint16 sizeOfBlock;
        in >> sizeOfBlock;
    }

    if (client->bytesAvailable() < blockSizeList->at(index))
    {
        return;
    }


    //std::cout << "LIST " << client->bytesAvailable() << std::endl;


    in >> playersList->at(index)->nick;
    in >> playersList->at(index)->moveLeft;
    in >> playersList->at(index)->moveRight;
    in >> playersList->at(index)->moveUp;
    in >> playersList->at(index)->moveDown;
    in >> playersList->at(index)->shoot;
    if (playersList->at(index)->shoot)
    {
        int mouseX, mouseY;
        in >> mouseX;
        in >> mouseY;

        if (playersList->at(index)->leftAmmo > 0)
        {
            float divider = (mouseY - playersList->at(index)->pos().y()) / (mouseX - playersList->at(index)->pos().x());
            float inverted = (mouseX < playersList->at(index)->pos().x()) ? M_PI : 0.0f;    // gdy mysz jest po lewej stronie, dodaj 180 st.
            float m_fAngle = atan(divider) + inverted;

            QPointF v = 200*QPointF(cos(m_fAngle), sin(m_fAngle));

            Bullet *bullet = new Bullet(map, playersList->at(index), playersList->at(index)->pos(), v);
            bulletsList->push_back(bullet);
            playersList->at(index)->leftAmmo--;
        }

    }

    // dodaj do tablicy
    tableWidget->setItem(index, 0, new QTableWidgetItem(playersList->at(index)->nick));
    tableWidget->setItem(index, 1, new QTableWidgetItem(QString::number(playersList->at(index)->fragsNr)));
    tableWidget->setItem(index, 2, new QTableWidgetItem(QString::number(playersList->at(index)->deathsNr)));

}


void MainWindow::sendData()
{

    // wyslij klientom taka sama strukture (zawierajaca tylko nicki, pozycje graczy i pociskow)

    QByteArray data;
    QDataStream out(&data, QIODevice::WriteOnly);
    out.setVersion(QDataStream::Qt_4_0);

//   out << blockSizeList->at(index);
    out << (quint16) 0;
    out << playersList->size();
    for (int i = 0; i < playersList->size(); ++i)
    {
        out << playersList->at(i)->nick;
        out << playersList->at(i)->pos().x();
        out << playersList->at(i)->pos().y();
        out << playersList->at(i)->fragsNr;
        out << playersList->at(i)->deathsNr;
    }
    out << bulletsList->size();
    for (int i = 0; i < bulletsList->size(); ++i)
    {
        out << bulletsList->at(i)->pos().x();
        out << bulletsList->at(i)->pos().y();
        out << bulletsList->at(i)->type;
    }

    out.device()->seek(0);

    out << (quint16) (data.size() - sizeof(quint16));

    for (int i = 0; i < clientsList->size(); ++i)
    {
        // if (i != index)
        {
            clientsList->at(i)->write(data);
        }
    }

    //(*blockSizeList)[index] = (quint16)0;

}


void MainWindow::About()
{
    AboutWindow *aboutWin = new AboutWindow;
    aboutWin->show();
}



// sprawdza, czy wystepuje kolizja miedzy dwoma ruchomymi obiektami
bool MainWindow::isCollision(const MovingObject* a, const MovingObject* b)
{
    if (pow(a->center().x() - b->center().x(), 2) +
            pow(a->center().y() - b->center().y(), 2) > pow(a->radius + b->radius, 2))
        return false;
    else
        return true;
}


// sprawdza, czy wystepuje kolizja miedzy mapa a ruchomym obiektem
bool MainWindow::isCollisionWithMap(const MovingObject* a)
{
    if (pow(a->center().x() - map->center().x(), 2) +
            pow(a->center().y() - map->center().y(), 2) < pow(map->radius - a->radius, 2))
        return false;
    else
    {
        return true;
    }
}

// sprawdza, czy wystepuje kolizja miedzy mapa a ruchomym obiektem i sprawdza punkt kolizji
bool MainWindow::isCollisionWithMap(const MovingObject* a, QPointF& point)
{
    if (pow(a->center().x() - map->center().x(), 2) +
            pow(a->center().y() - map->center().y(), 2) < pow(map->radius - a->radius, 2))
        return false;
    else
    {
        // znajdz punkt stycznosci
        point.setX(a->x());
        point.setY(a->y());
        return true;
    }
}

// iloczyn skalarny
qreal dot(const QPointF& p1, const QPointF& p2)
{
    return p1.x()*p2.x() + p1.y()*p2.y();
}


// normalizacja wektora
QPointF normalize(const QPointF& p)
{

    qreal fLength = sqrt(p.x()*p.x() + p.y()*p.y());

    if (fLength == 0.0f)
        return QPointF(0, 0);

    return QPointF(p.x()/fLength, p.y()/fLength);
}


// funkcja uruchamiana co 10ms
void MainWindow::GameTimer()
{

    QList<Bullet *>::iterator iterb;
    QList<Player *>::iterator iterp;

    // na podstawie predkosci pocisku ustal jego przesuniecie

    for (iterb = bulletsList->begin(); iterb != bulletsList->end(); iterb++)
    {
        // najpierw sprawdz, czy pocisk czasem nie zyje za dlugo
        if (((*iterb)->timer->elapsed() >= 4000))// || (!(*iterb)->isVisible()))
        {
            // zwieksz o 1 ilosc pociskow playerowi
            (*iterb)->owner->leftAmmo++;

            // usun z ekranu i wywal pocisk z listy
            graphicsView->scene()->removeItem(*iterb);
            bulletsList->erase(iterb);
            //iterb++;
        }
        else
        {
            // ustal droge jaka musi przebyc pocisk
            QPointF dist = (*iterb)->velocity * dT;
            (*iterb)->moveBy(dist.x(), dist.y());

            // sprawdz kolizje z mapa
            QPointF point;
            if (isCollisionWithMap(*iterb, point))
            {
                QPointF norm = QPointF(map->center().x() - point.x(), map->center().y() - point.y());
                norm = normalize(norm);
                (*iterb)->velocity.setX(0.9*(-2 * dot((*iterb)->velocity, norm) * norm.x() + (*iterb)->velocity.x()));
                (*iterb)->velocity.setY(0.9*(-2 * dot((*iterb)->velocity, norm) * norm.y() + (*iterb)->velocity.y()));
                (*iterb)->moveBy(-dist.x(), -dist.y());
            }

        }

    }


    // na podstawie nacisnietych klawiszy ustal przesuniecie gracza

    for (iterp = playersList->begin(); iterp != playersList->end(); ++iterp)
    {

        // sprawdz kolizje bulletow z playerami

        for (iterb = bulletsList->begin(); iterb != bulletsList->end(); ++iterb)
        {

            if (isCollision(*iterb, *iterp) && (*iterb)->owner != (*iterp) && (*iterb)->isVisible())
            {

                (*iterb)->owner->fragsNr++;
                (*iterb)->owner->leftAmmo++;

                (*iterp)->deathsNr++;
                (*iterp)->setPos(300, 300);
                (*iterp)->moveLeft = (*iterp)->moveRight = (*iterp)->moveUp = (*iterp)->moveDown = (*iterp)->shoot = false;
                (*iterp)->leftAmmo = 5;

                graphicsView->scene()->removeItem(*iterb);
                bulletsList->erase(iterb);

                break;

            }

        }

        // sprawdz kolizje mapy z playerem

        if ((*iterp)->moveDown)
        {
            (*iterp)->moveBy(0, 1);
            if (isCollisionWithMap((*iterp)))
                (*iterp)->moveBy(0, -1);
        }
        if ((*iterp)->moveUp)
        {
            (*iterp)->moveBy(0, -1);
            if (isCollisionWithMap((*iterp)))
                (*iterp)->moveBy(0, 1);
        }
        if ((*iterp)->moveRight)
        {
            (*iterp)->moveBy(1, 0);
            if (isCollisionWithMap((*iterp)))
                (*iterp)->moveBy(-1, 0);
        }
        if ((*iterp)->moveLeft)
        {

            (*iterp)->moveBy(-1, 0);
            if (isCollisionWithMap((*iterp)))
                (*iterp)->moveBy(1, 0);
        }
    }
}
